﻿using System.Linq;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Flatland.GeneticProgramming.CoreTypes;
using Flatland.Utilities;
using Microsoft.Office.Interop.Excel;

namespace Flatland.GeneticProgramming.FileWriter {
    /// <summary>
    /// Can write run results to files excel or .txt
    /// </summary>
    public class RunResultWriter {
        private Worksheet _workSheet;
        private GeneticProgramming.GP _gp;
        private Report _report;

        String title = "RunResult";
        String hFitness = "Max Fitness";
        String hAvgFitness = "Avg Fitness";

        /// <summary>
        /// Constructor with the GP of the run and the resulting best breed.
        /// </summary>
        /// <param name="gp"></param>
        /// <param name="genotype"></param>
        public RunResultWriter(GeneticProgramming.GP gp, Report report) {
            _gp = gp;
            _report = report;
        }

        /// <summary>
        /// Write the result to a file.
        /// </summary>
        /// <param name="folder">The folder</param>
        internal void Write(string folder) {
            //Check for excel
            Type officeType = Type.GetTypeFromProgID("Excel.Application");
            if (officeType == null) {
                WriteToFile(folder);
            }
            else {
                WriteToExcel(folder);
            }
        }

        /// <summary>
        /// Writes to run result to a .txt file and opens it
        /// </summary>
        /// <param name="folder"></param>
        private void WriteToFile(string folder) {
            String id = folder.Length == 0 ? Utils.GetTimeStamp() : folder;
            id += Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.Personal)).Count();
            string filePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "\\RunResult_" + id +
                              ".txt";
            StreamWriter sw = new StreamWriter(filePath);

            StringBuilder sb = new StringBuilder(_gp.MaxFitnessHistory.Count() * 10);
            sb.Append("Max fitness: ");
            for (int i = 0; i < _gp.CurrentGeneration; i++) {
                sb.Append(_gp.MaxFitnessHistory[i] + ", ");
            }
            sw.WriteLine(sb);
            sb.Clear();
            sb.Append("Avg fitness: ");
            for (int i = 0; i < _gp.CurrentGeneration; i++) {
                sb.Append(_gp.AverageFitnessHistory[i] + ", ");
            }
            sw.WriteLine(sb);

            sw.WriteLine();
            sw.WriteLine("Description --- Avg. time (ms) --- Total time (ms)");
            sw.WriteLine("Crossover --- " + _gp.SwCrossOver.ElapsedMilliseconds / _gp.CurrentGeneration + " --- " + _gp.SwCrossOver.ElapsedMilliseconds);

            sw.WriteLine("Mutation --- " + _gp.SwMutation.ElapsedMilliseconds / _gp.CurrentGeneration + " --- " + _gp.SwMutation.ElapsedMilliseconds);

            sw.WriteLine("Selection --- " + _gp.SwSelection.ElapsedMilliseconds / _gp.CurrentGeneration + " --- " + _gp.SwSelection.ElapsedMilliseconds);

            sw.WriteLine("Fitness --- " + _gp.SwFitness.ElapsedMilliseconds / _gp.CurrentGeneration + " --- " + _gp.SwFitness.ElapsedMilliseconds);


            sw.WriteLine("Run --- " + _gp.SwRunTime.ElapsedMilliseconds / _gp.CurrentGeneration + " --- " + _gp.SwRunTime.ElapsedMilliseconds);
            sw.WriteLine();

            sw.WriteLine("CrossOver GPTechnique: " + _gp.CrossoverTechnique);
            sw.WriteLine("Mutation GPTechnique: " + _gp.MutationTechnique);
            sw.WriteLine("Selection GPTechnique: " + _gp.SelectionTechnique);
            sw.WriteLine("Generation GPTechnique: " + _gp.CurrentGeneration);

            sw.WriteLine("Population size: " + _gp.PopulationSize);
            sw.WriteLine("Mutation rate: " + _gp.MutationRate);
            sw.WriteLine("Crossover GPTechnique: " + _gp.CrossOverRate);
            sw.WriteLine("Fitness goal: " + _gp.FitnessGoal);

            sw.WriteLine("GenoType: " + _report.GenoType.GetType().Name);
            sw.Close();
            Process.Start(filePath);
        }

        /// <summary>
        /// Writes the run result to Excel and opens it.
        /// </summary>
        /// <param name="folder"></param>
        private void WriteToExcel(string folder) {
            Application excel = new Application();
            excel.Visible = true;
            Workbook wb = excel.Workbooks.Add();
            wb.Title = title;
            Sheets sheets = wb.Worksheets;
            _workSheet = (Worksheet)sheets[1];

            WriteMaxFitness();
            WriteAvgFitness();
            WriteRunningTimes();
            WriteStats();
            String id = folder.Length == 0 ? Utils.GetTimeStamp() : folder;
            id += Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.Personal)).Count();
            try {
                _workSheet.SaveAs("RunResult_" + id + ".XLSX");
            }
            catch (COMException exception) {
                Console.WriteLine("No result written: " + exception);
            }
        }

        private void WriteMaxFitness() {
            _workSheet.Columns["V", Type.Missing].ColumnWidth = 15;
            _workSheet.Cells[1, 22] = "Max Fitness:";
            for (int i = 0; i < _gp.CurrentGeneration; i++) {
                _workSheet.Cells[i + 2, 22] = _gp.MaxFitnessHistory[i];
            }
            ChartObject chartObject = _workSheet.ChartObjects().Add(0, 0, 1000, 290);

            Chart chart = chartObject.Chart;
            var range = _workSheet.Range["v2", "v" + (_gp.CurrentGeneration + 2)];
            chart.SetSourceData(range);
            chart.ChartType = XlChartType.xlLine;
            chart.ChartWizard(Source: range, Title: hFitness, CategoryTitle: "Generation", ValueTitle: "Fitness");
        }

        /// <summary>
        /// Writes average fitness to the spread sheet.
        /// If avg fitness is under 0 it will be ignored for the graph
        /// </summary>
        private void WriteAvgFitness() {
            _workSheet.Columns["X", Type.Missing].ColumnWidth = 15;
            _workSheet.Cells[1, 24] = "Avg Fitness:";
            for (int i = 0; i < _gp.CurrentGeneration; i++) {
                if (_gp.AverageFitnessHistory[i] < 0)
                {
                    _workSheet.Cells[i + 2, 24] = 0;
                    _workSheet.Cells[i + 2, 25]  = _gp.AverageFitnessHistory[i]; 
                }
                else
                  _workSheet.Cells[i + 2, 24] = _gp.AverageFitnessHistory[i];
            }
            var chartObject = _workSheet.ChartObjects().Add(0, 300, 1000, 290);
            var chart = chartObject.Chart;
            var range = _workSheet.Range["x2", "x" + (_gp.CurrentGeneration + 2)];
            chart.SetSourceData(range);
            chart.ChartType = XlChartType.xlColumnClustered;
            chart.ChartWizard(Source: range, Title: hAvgFitness, CategoryTitle: "Generation", ValueTitle: "Fitness");
        }

        private void WriteRunningTimes() {
            _workSheet.Columns["A", Type.Missing].ColumnWidth = 15;
            _workSheet.Cells[41, 1] = "Description";
            _workSheet.Cells[41, 2] = "Avg. time (ms)";
            _workSheet.Cells[41, 3] = "Total time (ms)";

            _workSheet.Cells[42, 1] = "Crossover";
            _workSheet.Cells[42, 2] = _gp.SwCrossOver.ElapsedMilliseconds / _gp.CurrentGeneration;
            _workSheet.Cells[42, 3] = _gp.SwCrossOver.ElapsedMilliseconds;

            _workSheet.Cells[43, 1] = "Mutation";
            _workSheet.Cells[43, 2] = _gp.SwMutation.ElapsedMilliseconds / _gp.CurrentGeneration;
            _workSheet.Cells[43, 3] = _gp.SwMutation.ElapsedMilliseconds;

            _workSheet.Cells[44, 1] = "Selection";
            _workSheet.Cells[44, 2] = _gp.SwSelection.ElapsedMilliseconds / _gp.CurrentGeneration;
            _workSheet.Cells[44, 3] = _gp.SwSelection.ElapsedMilliseconds;

            _workSheet.Cells[45, 1] = "Fitness";
            _workSheet.Cells[45, 2] = _gp.SwFitness.ElapsedMilliseconds / _gp.CurrentGeneration;
            _workSheet.Cells[45, 3] = _gp.SwFitness.ElapsedMilliseconds;

            //_workSheet.Cells[46, 1] = "Compilation";
            //_workSheet.Cells[46, 2] = _gp.SwCompilation.ElapsedMilliseconds / _gp.CurrentGeneration;
            //_workSheet.Cells[46, 3] = _gp.SwCompilation.ElapsedMilliseconds;

            _workSheet.Cells[47, 1] = "Run";
            _workSheet.Cells[47, 2] = _gp.SwRunTime.ElapsedMilliseconds / _gp.CurrentGeneration;
            _workSheet.Cells[47, 3] = _gp.SwRunTime.ElapsedMilliseconds;
        }

        private void WriteStats() {
            _workSheet.Columns["E", Type.Missing].ColumnWidth = 25;
            _workSheet.Cells[42, 5] = "CrossOver GPTechnique";
            _workSheet.Cells[42, 6] = "" + _gp.CrossoverTechnique;
            _workSheet.Cells[43, 5] = "Mutation GPTechnique";
            _workSheet.Cells[43, 6] = "" + _gp.MutationTechnique;
            _workSheet.Cells[44, 5] = "Selection GPTechnique";
            _workSheet.Cells[44, 6] = "" + _gp.SelectionTechnique;
            _workSheet.Cells[45, 5] = "Initialization GPTechnique";
            _workSheet.Cells[45, 6] = "" + _gp.InitializationTechnique;
            _workSheet.Cells[46, 5] = "Generation";
            _workSheet.Cells[46, 6] = "" + _gp.CurrentGeneration;

            _workSheet.Cells[47, 5] = "Population size";
            _workSheet.Cells[47, 6] = _gp.PopulationSize;
            _workSheet.Cells[48, 5] = "Mutation rate";
            _workSheet.Cells[48, 6] = _gp.MutationRate;
            _workSheet.Cells[49, 5] = "Crossover rate";
            _workSheet.Cells[49, 6] = _gp.CrossOverRate;
            _workSheet.Cells[50, 5] = "Fitness goal";
            _workSheet.Cells[50, 6] = _gp.FitnessGoal;
            _workSheet.Cells[51, 5] = "GenoType";
            _workSheet.Cells[51, 6] = _report.GenoType.GetType().Name;
        }
    }
}